﻿
using System;
using System.Security.Cryptography;

namespace Helper
{
    /// <summary>
    /// This class converts a UTF-8 string into a cipher string, and vice versa.
    /// It uses 128-bit AES Algorithm in Cipher Block Chaining (CBC) mode with a UTF-8 key
    /// string and a UTF-8 initial vector string which are hashed by MD5. PKCS7 Padding is used
    /// as a padding mode and binary output is encoded by Base64.
    /// </summary>
    public class AESEncrypter
    {
        private System.Text.UTF8Encoding utf8Encoding;
        private RijndaelManaged aesAlg;


        /// <summary>
        /// Creates a StringEncrypter instance.
        /// </summary>
        /// <param name="key">A key string which is converted into UTF-8 and hashed by MD5.
        /// Null or an empty string is not allowed.</param>
        /// <param name="initialVector">An initial vector string which is converted into UTF-8
        /// and hashed by MD5. Null or an empty string is not allowed.</param>
        public AESEncrypter(string key, string initialVector)
        {
            if (key == null || key == "")
                throw new ArgumentException("The key can not be null or an empty string.", "key");

            if (initialVector == null || initialVector == "")
                throw new ArgumentException("The initial vector can not be null or an empty string.", "initialVector");



            // This is an encoder which converts a string into a UTF-8 byte array.
            this.utf8Encoding = new System.Text.UTF8Encoding();



            // Create a AES algorithm.
            this.aesAlg = new RijndaelManaged();

            // Set cipher and padding mode.
            this.aesAlg.Mode = CipherMode.CBC;
            this.aesAlg.Padding = PaddingMode.PKCS7;

            // Set key and block size.
            const int chunkSize = 128;

            this.aesAlg.KeySize = chunkSize;
            this.aesAlg.BlockSize = chunkSize;

            // Initialize an encryption key and an initial vector.
            MD5 md5 = new MD5CryptoServiceProvider();
            this.aesAlg.Key = md5.ComputeHash(this.utf8Encoding.GetBytes(key));
            this.aesAlg.IV = md5.ComputeHash(this.utf8Encoding.GetBytes(initialVector));
        }


        /// <summary>
        /// Encrypts a string.
        /// </summary>
        /// <param name="plainText">A string to encrypt. It is converted into UTF-8 before being encrypted.
        /// Null is regarded as an empty string.</param>
        /// <returns>An encrypted string.</returns>
        public string Encrypt(string plainText)
        {
            string returnString;

            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
            {
                returnString = "";
                //throw new ArgumentNullException("plainText");
            }
            else
            {
                try
                {
                    // Get an encryptor interface.
                    ICryptoTransform transform = this.aesAlg.CreateEncryptor();

                    // Get a UTF-8 byte array from a unicode string.
                    byte[] utf8Value = this.utf8Encoding.GetBytes(plainText);

                    // Encrypt the UTF-8 byte array.
                    byte[] encryptedValue = transform.TransformFinalBlock(utf8Value, 0, utf8Value.Length);

                    // Return a base64 encoded string of the encrypted byte array.
                    returnString = Convert.ToBase64String(encryptedValue);
                }
                finally
                {
                    // Clear the RijndaelManaged object.
                    if (aesAlg != null)
                        aesAlg.Clear();
                }
            }

            return returnString;
        }


        /// <summary>
        /// Decrypts a string which is encrypted with the same key and initial vector. 
        /// </summary>
        /// <param name="cipherText">A string to decrypt. It must be a string encrypted with the same key and initial vector.
        /// Null or an empty string is not allowed.</param>
        /// <returns>A decrypted string</returns>
        public string Decrypt(string cipherText)
        {
            string returnString;

            if (cipherText == null || cipherText == "")
            {
                returnString = "";
            }
            else
            {
                try
                {
                    // Get an decryptor interface.
                    ICryptoTransform transform = aesAlg.CreateDecryptor();

                    // Get an encrypted byte array from a base64 encoded string.
                    byte[] encryptedValue = Convert.FromBase64String(cipherText);

                    // Decrypt the byte array.
                    byte[] decryptedValue = transform.TransformFinalBlock(encryptedValue, 0, encryptedValue.Length);

                    // Return a string converted from the UTF-8 byte array.
                    returnString = this.utf8Encoding.GetString(decryptedValue);
                }
                finally
                {
                    // Clear the RijndaelManaged object.
                    if (aesAlg != null)
                        aesAlg.Clear();
                }
            }
            return returnString;
        }
    }
}
